package org.zpmis.api.business;

import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.map.HashedMap;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.zpmis.common.base.AdminLoginUser;
import org.zpmis.common.base.BaseWebService;
import org.zpmis.common.constant.UserConstants;
import org.zpmis.common.exception.BusinessException;
import org.zpmis.common.result.PlatformResult;
import org.zpmis.common.result.ResultCode;
import org.zpmis.common.utils.RedisUtil;
import org.zpmis.common.utils.StringUtil;
import org.zpmis.common.utils.json.GsonUtil;
import org.zpmis.config.JWTConfig;
import org.zpmis.domain.company.enums.CompanyEnum;
import org.zpmis.domain.company.model.Company;
import org.zpmis.domain.company.service.CompanyService;
import org.zpmis.domain.role.model.*;
import org.zpmis.domain.role.service.*;
import org.zpmis.domain.role.vo.SysMenuListVo;
import org.zpmis.domain.user.model.Employee;
import org.zpmis.domain.user.model.SysUser;
import org.zpmis.domain.user.model.User;
import org.zpmis.domain.user.qo.LoginQo;
import org.zpmis.domain.user.service.EmployeeService;
import org.zpmis.domain.user.service.SysUserService;
import org.zpmis.domain.user.service.UserService;
import org.zpmis.domain.user.service.WxApiService;
import org.zpmis.domain.user.vo.LoginVo;
import org.zpmis.util.JWTTokenUtil;
import tk.mybatis.mapper.entity.Condition;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created with CodeGenerator
 * Description:
 * @author  Yang
 * Date: 2021-6-2
 * Time: 14:44:14
 */
@Service
@Slf4j
public class UserWebService extends BaseWebService {
     @Reference
     private UserService userService;
     @Reference
     private SysUserService sysUserService;
     @Reference
     private SysUserRoleRelationService sysUserRoleRelationService;
     @Reference
     private SysRoleService sysRoleService;
     @Reference
     private SysRolePermissionRelationService sysRolePermissionRelationService;
     @Reference
     private SysPermissionService sysPermissionService;
     @Reference
     private SysRoleMenuRelationService sysRoleMenuRelationService;
     @Reference
     private SysMenuService sysMenuService;
     @Reference
     private WxApiService wxApiService;

     @Reference
     private EmployeeService employeeService;

     @Reference
     private CompanyService companyService;


     /**
      * 用户登录
      * @param loginQo
      * @return
      * @throws BusinessException
      * @throws Exception
      */
     public PlatformResult login(LoginQo loginQo) throws BusinessException,Exception{

          String userName = loginQo.getUserName();
          String password = loginQo.getPassword();
          String appCode = loginQo.getAppCode();
          String USER_INFO_REDIS_KEY;
          String USER_TOKEN_REDIS_KEY;
          String USER_LOGIN_DETAIL_KEY;
          String ISSUER;
          LoginVo loginVo;
          if(StringUtil.isNotEmpty(appCode)){//小程序登录
               //根据code 获取openid
               String openId = wxApiService.getWxOpenIdByCode(appCode);
               if(StringUtil.isEmpty(openId)){
                    log.info("[微信小程序通过code获取openid失败]---result:{}",openId);
                    throw new BusinessException(ResultCode.PARAM_ERROR);
               }
               //String session_key = resObj.getString("session_key");
               //小程序登录 通过openid 获取用户信息
               User user = userService.findBy("wxOpenid", openId);
               /**
                * 用户校验
                */
               if (user == null) {//用户未找到
                    throw new BusinessException(ResultCode.USER_NOT_EXIST);
               }
               if (!user.getEnabled()) {//账号不可用
                    throw new BusinessException(ResultCode.USER_ACCOUNT_DISABLE);
               }
               if (!user.getAccountNonLocked()) {//账号被锁定
                    throw new BusinessException(ResultCode.USER_ACCOUNT_LOCKED);
               }
               if (!user.getAccountNonExpired()) {//账号已过期
                    throw new BusinessException(ResultCode.USER_ACCOUNT_EXPIRED);
               }
               if(!user.getCredentialsNonExpired()){
                    throw new BusinessException(ResultCode.USER_CREDENTIALS_EXPIRED);
               }
               //小程序不用校验密码
               /*if (!new BCryptPasswordEncoder().matches(password, user.getPassword())) {//校验密码是否正确
                    throw new BusinessException(ResultCode.USER_CREDENTIALS_ERROR);
               }*/
               // 组装参数
               loginVo = LoginVo.builder()
                       .id(user.getId())
                       .name(user.getName())
                       .loginType("1")
                       .userName(user.getUserName())
                       .companyId(user.getCompanyId())
                       .isAccountNonExpired(user.getAccountNonExpired())
                       .isAccountNonLocked(user.getAccountNonLocked())
                       .isCredentialsNonExpired(user.getCredentialsNonExpired())
                       .isEnabled(user.getEnabled())
                       .build();
               Long companyId = user.getCompanyId();
               if(companyId != null){
                    Company company = companyService.findById(companyId);
                    if(company != null && company.getIsDeleted() != CompanyEnum.DELETED.code()){
                         loginVo.setUserType(company.getUserType());
                    }
               }
               //更新最后登录时间
               userService.update(User.builder().id(user.getId()).lastLoginTime(Instant.now().toEpochMilli()).build());
               USER_INFO_REDIS_KEY = UserConstants.APPLETS_USER_INFO_REDIS_KEY;
               USER_TOKEN_REDIS_KEY = UserConstants.APPLETS_USER_TOKEN_REDIS_KEY;
               USER_LOGIN_DETAIL_KEY = UserConstants.APPLETS_USER_LOGIN_DETAIL_KEY;
               ISSUER = UserConstants.APPLETS_ISSUER;
          }else{
               //后台管理登录
               // 查询用户信息
               SysUser sysUser =sysUserService.findBy("userName",userName);
               /**
                * 用户校验
                */
               if(sysUser == null){//用户未找到
                    throw new BusinessException(ResultCode.USER_NOT_EXIST);
               }
               if(!sysUser.getEnabled()){//账号不可用
                    throw new BusinessException(ResultCode.USER_ACCOUNT_DISABLE);
               }
               if(!sysUser.getAccountNonLocked()){//账号被锁定
                    throw new BusinessException(ResultCode.USER_ACCOUNT_LOCKED);
               }
               if(!sysUser.getAccountNonExpired()){//账号已过期
                    throw new BusinessException(ResultCode.USER_ACCOUNT_EXPIRED);
               }
               if(!sysUser.getCredentialsNonExpired()){//密码已过期
                    throw new BusinessException(ResultCode.USER_CREDENTIALS_EXPIRED);
               }
               if(!new BCryptPasswordEncoder().matches(password,sysUser.getPassword())){//校验密码是否正确
                    throw new BusinessException(ResultCode.USER_CREDENTIALS_ERROR);
               }
               USER_INFO_REDIS_KEY = UserConstants.ADMIN_USER_INFO_REDIS_KEY;
               USER_TOKEN_REDIS_KEY = UserConstants.ADMIN_USER_TOKEN_REDIS_KEY;
               USER_LOGIN_DETAIL_KEY = UserConstants.ADMIN_USER_LOGIN_DETAIL_KEY;
               ISSUER = UserConstants.ADMIN_ISSUER;
               //当redis中有当前登录用户的信息时， 跳过查询数据库权限相关信息
               Object redisLoginUserVo = redisUtil.get(String.format(USER_LOGIN_DETAIL_KEY, sysUser.getId()));
               if(redisLoginUserVo != null){
                    loginVo = GsonUtil.toPojo(redisLoginUserVo.toString(),LoginVo.class);
               }else{
                    //用户权限code集合
                    List<String> perCodeList = new ArrayList<>();
                    //用户角色 roleName
                    List<String> roleList = new ArrayList<>();
                    //用户菜单
                    List<SysMenuListVo> menuList = new ArrayList<>();

                    //查询用户菜单
                    // 根据用户角色的关联表 查询用户角色
                    List<SysRole> sysRoleEntityList = new ArrayList<>();
                    Map<String,Object> param = new HashMap<>();
                    param.put("userId",sysUser.getId());
                    List<SysUserRoleRelation> sysUserRoleRelations = sysUserRoleRelationService.findByConditionMap(param);
                    if(sysUserRoleRelations != null && !sysUserRoleRelations.isEmpty()){
                         String ids = sysUserRoleRelations.stream().map(sysUserRoleRelation -> sysUserRoleRelation.getRoleId().toString()).collect(Collectors.joining(","));
                         sysRoleEntityList = sysRoleService.findByIds(ids);
                         //排除超级用户
                         if(sysUser.getUserName().equals(UserConstants.ROOT)){
                              sysRoleEntityList = sysRoleService.findAll();
                         }
                         //拼装用户角色集合
                         roleList = sysRoleEntityList.stream().map(SysRole::getRoleName).collect(Collectors.toList());
                    }
                    //查询用户拥有的权限
                    List<SysPermission> permissionList = new ArrayList<>();
                    //根据角色id 查询角色和权限关联表
                    if(sysRoleEntityList != null && !sysRoleEntityList.isEmpty()) {
                         List<Long> roleIds = sysRoleEntityList.stream().map(SysRole::getId).collect(Collectors.toList());

                         Condition condition = new Condition(SysRolePermissionRelation.class);
                         condition.createCriteria()
                                 .andIn("roleId", roleIds);
                         List<SysRolePermissionRelation> rpRelations = sysRolePermissionRelationService.findByCondition(condition);
                         if (rpRelations != null && !rpRelations.isEmpty()) {
                              String pids = rpRelations.stream().map(sysRolePermissionRelation -> sysRolePermissionRelation.getPermissionId().toString()).collect(Collectors.joining(","));
                              permissionList = sysPermissionService.findByIds(pids);
                              //排除超级用户
                              if(sysUser.getUserName().equals(UserConstants.ROOT)){
                                   permissionList = sysPermissionService.findAll();
                              }
                         }
                         if (!permissionList.isEmpty()) {
                              perCodeList = permissionList.stream().filter(sysPermission -> !sysPermission.getIsDeleted()).map(SysPermission::getPermissionCode).collect(Collectors.toList());
                         }
                    }
                    //根据用户角色菜单关联 查询用户所拥有的菜单集合
                    if(!sysRoleEntityList.isEmpty()){
                         List<Long> roleIds = sysRoleEntityList.stream().map(SysRole::getId).collect(Collectors.toList());
                         Condition roleMenuCondition = new Condition(SysRoleMenuRelation.class);
                         Example.Criteria criteria = roleMenuCondition.createCriteria();
                         criteria.andIn("roleId",roleIds);
                         List<SysRoleMenuRelation> roleMenuRelations = sysRoleMenuRelationService.findByCondition(roleMenuCondition);

                         String menuIds = roleMenuRelations.stream().map(m -> m.getMenuId().toString()).collect(Collectors.joining(","));
                         List<SysMenu> sysMenuList = sysMenuService.findByIds(menuIds);
                         //排除超级用户
                         if(sysUser.getUserName().equals(UserConstants.ROOT)){
                              sysMenuList = sysMenuService.findAll();
                         }

                         List<Long> userMenuIdList = sysMenuList.stream().map(SysMenu::getId).collect(Collectors.toList());
                         menuList = sysMenuList.stream().filter(sysMenu -> sysMenu.getParentId() == 0 && sysMenu.getType() == 1 && sysMenu.getStatus() == 1 && sysMenu.getIsDeleted() == 0).map(SysMenuListVo :: new).sorted(Comparator.comparing(SysMenuListVo::getSort,Comparator.nullsFirst(Integer :: compareTo))).collect(Collectors.toList());
                         this.recursiveQueryMenu(menuList,perCodeList,userMenuIdList);
                    }

                    //封装返回对象
                    loginVo = LoginVo.builder()
                            .id(sysUser.getId())
                            .userName(sysUser.getUserName())
                            .name(sysUser.getUserName())
                            .loginType("2")
                            .isAccountNonExpired(sysUser.getAccountNonExpired())
                            .isAccountNonLocked(sysUser.getAccountNonLocked())
                            .isCredentialsNonExpired(sysUser.getCredentialsNonExpired())
                            .isEnabled(sysUser.getEnabled())
                            .authorities(perCodeList)
                            .roles(roleList)
                            .sysMenuList(menuList)
                            .build();
                    //查询后台用户组织id和姓名
                    Map<String,Object> employeeParam = new HashMap<>();
                    employeeParam.put("isDeleted",0);
                    employeeParam.put("sysUid",sysUser.getId());
                    employeeParam.put("status",1);
                    List<Employee> employeeList = employeeService.findByConditionMap(employeeParam);
                    if(!employeeList.isEmpty()){
                         Employee employee = employeeList.stream().findFirst().get();
                         loginVo.setCompanyId(employee.getOrgId());
                         loginVo.setName(employee.getName());
                    }
               }

               //更新最后登录时间
               sysUserService.update(SysUser.builder().id(sysUser.getId()).lastLoginTime(Instant.now().toEpochMilli()).build());
          }
          //生成token 并设置签发者
          String token = JWTTokenUtil.createAccessToken(loginVo,ISSUER);
          token = JWTConfig.tokenPrefix + token;
          Object redisToken = redisUtil.get(String.format(USER_INFO_REDIS_KEY, loginVo.getId()));
          //删除之前的token
          if(redisToken != null){
               redisUtil.del(String.format(USER_TOKEN_REDIS_KEY,redisToken.toString()));
          }
          //此处还可以进行一些处理，比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限，
          //进而前台动态的控制菜单的显示等，具体根据自己的业务需求进行扩展
          redisUtil.set(String.format(USER_TOKEN_REDIS_KEY,token), GsonUtil.toJsonString(loginVo),JWTConfig.redisExpiration);
          //根据用户id 存储用户的token
          redisUtil.set(String.format(USER_INFO_REDIS_KEY,loginVo.getId()),token,JWTConfig.redisExpiration);
          //设置用户登录信息保存
          redisUtil.set(String.format(USER_LOGIN_DETAIL_KEY,loginVo.getId()), GsonUtil.toJsonString(loginVo));
          // 封装返回参数
          Map<String,Object> resultData = new HashMap<>();
          resultData.put("msg", "登录成功");
          resultData.put("token",token);
          resultData.put("loginUser",loginVo);
          return PlatformResult.success(resultData);
     }


     /**
      * 登出
      */
     public void logout() throws BusinessException,Exception{
          String tokenHeader = super.getTokenFromHeader();
          String USER_INFO_REDIS_KEY;
          String USER_TOKEN_REDIS_KEY;
          if (null!=tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {
               // 解析JWT
               Claims claims = null;
               try {
                    // 截取JWT前缀
                    String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");
                    claims = Jwts.parser()
                            .setSigningKey(JWTConfig.secret)
                            .parseClaimsJws(token)
                            .getBody();
                    //获取签发者
                    String issuer = claims.getIssuer();
                    //根据签发者获取不同的redis前缀 从而退出不同系统登录的用户
                    USER_INFO_REDIS_KEY = UserConstants.getUserInfoRedisKey(issuer);
                    USER_TOKEN_REDIS_KEY = UserConstants.getUserTokenRedisKey(issuer);
                    if(StringUtil.isEmpty(USER_INFO_REDIS_KEY) || StringUtil.isEmpty(USER_TOKEN_REDIS_KEY)){
                         throw new BusinessException(ResultCode.TOKEN_ERROR);
                    }
                    //删除redis用户登录信息
                    redisUtil.del(String.format(USER_INFO_REDIS_KEY,claims.getId()));
                    redisUtil.del(String.format(USER_TOKEN_REDIS_KEY,tokenHeader));
               }catch (Exception e){
                    log.error("[UserWebService]---loginOut---Token验证失败，Exception：",e);
                    throw new BusinessException(ResultCode.TOKEN_ERROR);
               }
          }
     }

     /**
      *
      * @param parentMenuListVo 用户关联的顶级菜单集合
      * @param perCodeList  用户的权限
      * @param userMenuIdList 用户关联的菜单id
      * @return
      */
     private List<SysMenuListVo> recursiveQueryMenu(List<SysMenuListVo> parentMenuListVo,List<String> perCodeList,List<Long> userMenuIdList){
          //循环顶级菜单 放入下级菜单

          parentMenuListVo.stream().forEach(sysMenuVo -> {
               Long nextParentId = sysMenuVo.getId();
               Long parentId = sysMenuVo.getParentId();
               if(nextParentId == parentId){
                    //如果当前对象的 父id 等于本身的id 则证明数据有误，会造成死循环，所以这里判断以免造成死循环
                    return ;
               }
               Condition condition = new Condition(SysMenu.class);
               condition.createCriteria()
                       .andEqualTo("isDeleted",0)
                       .andEqualTo("status",1)
                       .andEqualTo("parentId",sysMenuVo.getId())
                       .andIn("id",userMenuIdList);
               condition.orderBy("sort").asc();
               List<SysMenu> childMenuList = sysMenuService.findByCondition(condition);
               List<SysMenuListVo> removeChildMenu = new ArrayList<>();
               if(!childMenuList.isEmpty()){
                    List<SysMenuListVo> sysChildMenuListVoList = childMenuList.stream().map(SysMenuListVo::new).collect(Collectors.toList());
                    sysChildMenuListVoList.stream().forEach(childMenu -> {
                         //判断当前菜单是否有对应的权限
                         String routerUrl = childMenu.getRouterUrl();
                         Map<String,Object> permissionMap = new HashedMap();
                         permissionMap.put("apiUrl",routerUrl);
                         permissionMap.put("isDeleted",0);
                         List<SysPermission> uriPermissionList = sysPermissionService.findByConditionMap(permissionMap);
                         if(uriPermissionList != null && !uriPermissionList.isEmpty()){
                              //该接口需要的权限
                              List<String> uriNeedPermissions = uriPermissionList.stream().map(SysPermission::getPermissionCode).collect(Collectors.toList());

                              //判断用户所拥有的权限中是否包含该按钮权限
                              //当这两个集合中不存在公共元素的时候返回true 存在公共元素就返回false
                              if(Collections.disjoint(perCodeList,uriNeedPermissions)){
                                   //parentMenuListVo.remove(sysMenuVo);
                                   removeChildMenu.add(childMenu);
                              }
                         }
                    });
                    //移除没有权限的 菜单
                    sysChildMenuListVoList.removeAll(removeChildMenu);
                    //放入子菜单集合
                    sysMenuVo.getChildList().addAll(sysChildMenuListVoList);
                    this.recursiveQueryMenu(sysChildMenuListVoList,perCodeList,userMenuIdList);
               }

          });

          return parentMenuListVo;
     }
}

